/**
 *  @file ToneDetector.cpp
 *
 *  @date 16.04.2014
 *  @author Armin Joachimsmeyer
 *  Email: armin.joachimsmeyer@gmail.com
 *  License: GPL v3 (http://www.gnu.org/licenses/gpl.html)
 *  Sources: https://code.google.com/p/arduino-tone-detector/
 *
 *  @version 1.0.3
 *
 *  @brief analyzes a microphone signal and toggles an output pin, if the main frequency is for an specified duration in a specified range.
 *  It serves as a tone detector for a whistle tone pitch which operates an mains relay.
 *  By using different pitches it is possible to control multiple tone relays in a single room.
 *  If the pitch is lower than the specified frequency the feedback LED blinks slowly, if the pitch is higher it blinks fast.
 *
 *	RESET
 *  After power up or reset the feedback LED echoes the range number.
 *  A reset can be performed by power off/on or by pressing the button shorter than RESET_ENTER_BUTTON_PUSH_MILLIS / 0.12 seconds
 *  within RESET_WAIT_TIMEOUT_MILLIS /0.3 seconds two times.
 *
 *	PROGRAMMING
 *  Programming is done by pressing the button longer than BUTTON_PUSH_ENTER_PROGRAM_SIMPLE_MILLIS / 1.5 seconds
 *  but less than BUTTON_PUSH_ENTER_PROGRAM_ADVANCED_MILLIS / 4 seconds.
 *  After releasing the button the feedback LED blinks once for entering programming mode.
 *  Now press the button once for range 1, twice for range 2 etc. Each button press is echoed by the feedback LED.
 *  Waiting for PROGRAM_MODE_SIMPLE_END_DETECT_MILLIS / 1.5 seconds ends the programming mode
 *  and the feedback LED echoes the number of button presses recognized.
 *  The needed duration of tone match to toggle the relay is fixed at MATCH_MILLIS_NEEDED_DEFAULT / 1.2 seconds.
 *
 *	ADVANCED PROGRAMMING
 *  Advanced programming is done by pressing the button longer than BUTTON_PUSH_ENTER_PROGRAM_ADVANCED_MILLIS / 4 seconds.
 *  After releasing the feedback LED blinks twice.
 *  Now whistle the tone you want to detect, then press the button again.
 *  While you press the button the tone range is measured. i.e. the minimum and maximum frequency of the tone you are whistling is stored.
 *  If you press the button again before the PROGRAM_MODE_ADVANCED_END_DETECT_MILLIS / 3 sec timeout
 *  the duration of this second press is taken as the needed duration for the tone match to toggle the relay.
 *  Otherwise the  MATCH_MILLIS_NEEDED_DEFAULT / 1.2 seconds are taken.
 *  After timeout of PROGRAM_MODE_TIMEOUT_MILLIS / 5 seconds the advanced programming mode is ended
 *  and the effective duration is echoed by the feedback LED.
 *
 *  PREDEFINED RANGES
 *  1   1750 - 2050 Hz  -> 300 Hz
 *  2   1500 - 1740 Hz  -> 250 Hz
 *  3   1300 - 1490 Hz  -> 200 Hz
 *  4   1150 - 1290 Hz  -> 150 Hz
 *  5   1000 - 1140 Hz  -> 150 Hz
 *  6    900 -  990 Hz  -> 100 Hz
 *
 *  7   1550 - 1900 Hz  -> 350 Hz
 *  8   1250 - 1540 Hz  -> 300 Hz
 *  9   1000 - 1240 Hz  -> 250 Hz
 *
 */

#if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega328__)
#define DEBUG
#endif
#if defined(__AVR_ATtiny85__)
//#define TINY_USE_SERIAL
//#define DEBUG
#endif

#include <Arduino.h>
#include "toneDetector.h"
/*
 * I can whistle from 550 to 2000 Hz (and do it easy from 950 - 1800)
 * Ranges are in units of 10Hz, so 175 => 1750Hz
 */
uint8_t predefinedRangesStart[] = { 175, 150, 130, 115, 100, 90, 155, 125, 100 };
uint8_t predefinedRangesEnd[] = { 205, 174, 149, 129, 114, 99, 190, 154, 124 };
#include <avr/eeprom.h>
#include <avr/wdt.h>

#include "digitalWriteFast.h"
#define digitalToggleFast(P) BIT_SET(* &PINB, __digitalPinToBit(P))

// ATMEL ATTINY85 / ARDUINO
//
//                              +-\/-+
//             Ain0 (D 5) PB5  1|    |8  Vcc
//    BUTTON - Ain3 (D 3) PB3  2|    |7  PB2 (D 2) Ain1 - SIGNAL_IN
// RELAY_OUT - Ain2 (D 4) PB4  3|    |6  PB1 (D 1) pwm1 - DEBUG
//                        GND  4|    |5  PB0 (D 0) pwm0 - LED_FEEDBACK
//
// ATMEL ATMEGA328 / ARDUINO
//
//                  +-\/-+
//            PC6  1|    |28  PC5 (AI 5)
//      (D 0) PD0  2|    |27  PC4 (AI 4)
//      (D 1) PD1  3|    |26  PC3 (AI 3) SIGNAL_IN
//      (D 2) PD2  4|    |25  PC2 (AI 2)
// PWM+ (D 3) PD3  5|    |24  PC1 (AI 1)
//      (D 4) PD4  6|    |23  PC0 (AI 0)
//            VCC  7|    |22  GND
//            GND  8|    |21  AREF
//            PB6  9|    |20  AVCC
//            PB7 10|    |19  PB5 (D 13) LED_FEEDBACK
// PWM+ (D 5) PD5 11|    |18  PB4 (D 12) RELAY_OUT
// PWM+ (D 6) PD6 12|    |17  PB3 (D 11) PWM
//      (D 7) PD7 13|    |16  PB2 (D 10) PWM
//      (D 8) PB0 14|    |15  PB1 (D 9)  PWM
//                  +----+

/*
 *      + 5V                             _____                                         + 5V
 *      |                             --|_____|---                                     |
 *      _                             |   1M     |                                    ____
 *     | |                            |          |                                    \  / LED
 *     | | 4k7                        |___|\     |                                     \/  ->
 *     |_|                            |  2| \____|_______\ SIGNAL_IN                  ----
 *      |    ____             ____    |   | /6           /                            C/
 *      |---|____|-----------|____|---|---|/                      LED_FEEDBACK  \____|/  NPN
 *      |     4k7      |      10k     |  3                                      /  B |\
 *     ---            |O MIC          _    LM308                                      E\
 *     --- 1 uF        |             | |                                                |
 *      |              |             | | 10k                                            _ 
 *     ___            ___            |_|                                               | |
 *                                    |                                                | | 3k3
 *                                    |                                                |_|
 *                                   ---                                                |
 *                                   ---  100 nF                                        | 
 *                                    |                                                ___
 *                                   ___  
 *                                
 *                                
 *        
 */

#if defined(__AVR_ATtiny85__)
#if defined TINY_USE_SERIAL
#include "TinyDebugSerial.h"
#define DEFAULT_TO_TINY_DEBUG_SERIAL
#endif

/*
 * Attiny85
 */
#define PRESCALE_VALUE PRESCALE4 // 52 microseconds per ADC sample => 19,23kHz
// since digitalWriteFast.h does not support attiny
#undef digitalWriteFast
#define digitalWriteFast(P, V) \
if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \
  BIT_WRITE(* &PORTB, __digitalPinToBit(P), (V)); \
} else { \
  digitalWrite((P), (V)); \
}

#undef digitalReadFast
#undef __digitalReadFast
#define digitalReadFast(P) ( (int) __digitalReadFast((P)) )
#define __digitalReadFast(P) \
(__builtin_constant_p(P)) \
  ? BIT_READ(* &PINB, __digitalPinToBit(P)) \
  : digitalRead((P))

#define BUTTON_PIN 3
#define RELAY_OUT 4
#define LED_FEEDBACK 0
#define LED_DEBUG 1

#define ADC_CHANNEL 1 // input Ain1 (PB2)
#define ADC_REFERENCE 0
#endif

/*
 * Boarduino
 */
#if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega328__)

#define PRESCALE_VALUE PRESCALE64 // 52 microseconds per ADC sample => 19,23kHz
#define BUTTON_PIN 5
#define RELAY_OUT  12
#define LED_FEEDBACK  13
#define LED_DEBUG  10

#define ADC_CHANNEL 3 // input A3
#define ADC_REFERENCE 1 // 1=VCC  3(INTERNAL)=1.1V
#endif

#define LED_LOWER  8
#define LED_HIGHER 9
#define LED_PLAUSI_FIRST  11
#define LED_PLAUSI_DISTRIBUTION  6

// plausibility for frequency in advanced programming mode
#define MAX_ACCEPTABLE_DELTA_FREQ 10 // max delta Freq10Hz for frequency change between two consecutive measurement in advanced programming mode
//
/*
 * States for main loop
 */
enum MainStateEnum {
    TONE_DETECT,
    WAIT_FOR_SECOND_PUSH_FOR_RESET,
    PROGRAM_SIMPLE,
    PROGRAM_ADVANCED_TONE_RANGE,
    PROGRAM_ADVANCED_TONE_DURATION,
    PROGRAM_WAIT_FOR_FEEDBACK_END
};

/*
 * Timing
 */
#define MATCH_MILLIS_NEEDED_DEFAULT 1200    // number of valid period matches before relay toggle => 1200 ms

#define MATCH_TO_LONG_MILLIS  600  // max milliseconds for match condition true, otherwise switch back to relay state before
#define TIMING_RELAY_DEAD_MILLIS  2500  // min milliseconds between 2 changes of relay state
#define BUTTON_PUSH_DEBOUNCE_MILLIS 20 // must be smaller than 65!
// LED blink timing
#define TIMING_LOWER_MILLIS     150
#define TIMING_HIGHER_MILLIS    50
#define TIMING_HIGHER_MILLIS_MINIMUM    10
/*
 * Timing for reset
 */
#define RESET_ENTER_BUTTON_PUSH_MILLIS 120  // milliseconds for first push of double push to perform reset
#define RESET_WAIT_TIMEOUT_MILLIS 300  // milliseconds to push and release button the second time in order to perform reset command
/*
 * Timing for range programming
 */
#define BUTTON_PUSH_ENTER_PROGRAM_SIMPLE_MILLIS 1500  // milliseconds to enter simple program mode
#define BUTTON_PUSH_ENTER_PROGRAM_ADVANCED_MILLIS 4000  // milliseconds to enter advanced program mode
#define PROGRAM_MODE_TIMEOUT_MILLIS 5000  // milliseconds to enter regular detect mode if in program mode and no button press happens
#define PROGRAM_MODE_SIMPLE_END_DETECT_MILLIS 1500  // milliseconds to enter regular detect mode if in simple program mode and a range has been chosen and no button press happens
#define PROGRAM_MODE_ADVANCED_END_DETECT_MILLIS 3000  // milliseconds to enter regular detect mode if in advanced program mode and a range has been entered and no button press happens
//

struct EepromParameterStruct {
    uint16_t PeriodValidNeededMillis; // ms needed for accepting match
    uint16_t Frequency10HzMin;
    uint16_t Frequency10HzMax;
};
EepromParameterStruct EepromParameter;
#define PARAMETER_EEPROMADDR (E2END -1 - 3*sizeof(EepromParameterStruct)) //eeprom address for match parameter data
struct PfeiffschalterControlStruct {

    uint16_t Frequency10HzLast; // for advanced programming mode

    // status for main loop
    MainStateEnum MainState;

    uint8_t ButtonPressCounter;

    volatile uint16_t LedBlinkMillis;
    volatile int8_t LedBlinkCount; // 0 stop blinking, -1 => blink forever

    int16_t MatchValidCount; // count for valid matches after last STATE_LED_OFF
    int16_t MatchValidNeeded; // valid matches detected needed for accepting match := PeriodValidNeededMillis/26.6
    uint16_t PeriodValidNeededMillis;

} PfeiffschalterControl;

struct ButtonControlStruct {
    volatile uint32_t MillisAtLastInterrupt;
    volatile bool LowDetected;
    volatile bool ButtonReleaseJustDetected;
    volatile uint16_t MillisOfButtonPress;
} ButtonControl;

/*******************************************************************************************
 * Function declaration section
 *******************************************************************************************/

void toggleOutput(uint8_t aOutputPinNumber) {
    *portInputRegister(digitalPinToPort(aOutputPinNumber)) = digitalPinToBitMask(aOutputPinNumber);
}

/*
 * aLedBlinkCount = -1 -> blink forever
 */
void setFeedbackLedBlinkState(uint16_t aLedBlinkMillis, int8_t aLedBlinkCount) {
    PfeiffschalterControl.LedBlinkMillis = aLedBlinkMillis;
    PfeiffschalterControl.LedBlinkCount = aLedBlinkCount;
}

void backToStateDetect(void) {
    PfeiffschalterControl.MainState = TONE_DETECT;
    ToneDetectorControl.ToneMatch = TONE_MATCH_INVALID;
    PfeiffschalterControl.LedBlinkCount = 0;
}

void eepromWriteParameter(void) {

    EepromParameter.Frequency10HzMin = ToneDetectorControl.Frequency10HzMin;
    EepromParameter.Frequency10HzMax = ToneDetectorControl.Frequency10HzMax;
    EepromParameter.PeriodValidNeededMillis = PfeiffschalterControl.PeriodValidNeededMillis;
    eeprom_write_block((void*) &EepromParameter, (void*) PARAMETER_EEPROMADDR, sizeof(EepromParameterStruct));
}

void eepromReadParameter(void) {
    eeprom_read_block((void*) &EepromParameter, (void*) PARAMETER_EEPROMADDR, sizeof(EepromParameterStruct));
    PfeiffschalterControl.PeriodValidNeededMillis = EepromParameter.PeriodValidNeededMillis;
    PfeiffschalterControl.MatchValidNeeded = EepromParameter.PeriodValidNeededMillis / 27;
    ToneDetectorControl.Frequency10HzMin = EepromParameter.Frequency10HzMin;
    ToneDetectorControl.Frequency10HzMax = EepromParameter.Frequency10HzMax;
}

void setPeriodValidNeededMillis(uint16_t aPeriodValidNeededMillis) {
    PfeiffschalterControl.PeriodValidNeededMillis = aPeriodValidNeededMillis;
    PfeiffschalterControl.MatchValidNeeded = aPeriodValidNeededMillis / 27;
}

/*
 * echo range index
 */
void echoRangeIndex(void) {
    // search range index
    uint8_t i;
    for (i = 0; i < sizeof(predefinedRangesStart); ++i) {
        if (ToneDetectorControl.Frequency10HzMin == predefinedRangesStart[i]
                && ToneDetectorControl.Frequency10HzMax == predefinedRangesEnd[i]) {
            break;
        }
    }
    // display range index
    for (uint8_t j = 0; j <= i; ++j) {
        digitalWriteFast(LED_FEEDBACK, HIGH);
        delay(TIMING_LOWER_MILLIS * 3);
        digitalWriteFast(LED_FEEDBACK, LOW);
        delay(TIMING_LOWER_MILLIS * 3);
    }
}

// Example for placing code at init sections see: http://www.nongnu.org/avr-libc/user-manual/mem_sections.html
void MyInit(void) __attribute__ ((naked)) __attribute__ ((section (".init8")));
void MyInit(void) {
}
/*******************************************************************************************
 * Program code starts here
 * Setup section
 *******************************************************************************************/
void setup() {

    /* Clear flags in MCUSR */
    MCUSR = 0x00;

    /*
     * for Boarduino with bootloader it comes too late here, since after reset the watchdog is still enabled
     * but with fastest prescaler value (approximately 15 ms)
     */ //
    wdt_disable();

#if (__AVR_ATmega328P__) || defined (__AVR_ATmega328__)
#ifdef DEBUG
    Serial.begin(115200);
#endif
    pinMode(LED_LOWER, OUTPUT);
    pinMode(LED_HIGHER, OUTPUT);
    pinMode(LED_PLAUSI_FIRST, OUTPUT);
    pinMode(LED_PLAUSI_DISTRIBUTION, OUTPUT);
#else
#ifdef DEBUG
    Serial.begin(9600);
    /*Serial.begin(38400);*/
#endif
#endif

    pinMode(LED_FEEDBACK, OUTPUT);
    pinMode(RELAY_OUT, OUTPUT);
    pinMode(LED_DEBUG, OUTPUT);

    pinMode(BUTTON_PIN, INPUT_PULLUP);

    // init state
    backToStateDetect();
    ButtonControl.ButtonReleaseJustDetected = false;
    ButtonControl.LowDetected = false;

    // get parameter from eeprom
    eepromReadParameter();
    if (ToneDetectorControl.Frequency10HzMin < 30 || ToneDetectorControl.Frequency10HzMin > 500) {
        //eeprom not filled -> start with reasonable values
        ToneDetectorControl.Frequency10HzMin = predefinedRangesStart[1];
        ToneDetectorControl.Frequency10HzMax = predefinedRangesEnd[1];
        setPeriodValidNeededMillis(MATCH_MILLIS_NEEDED_DEFAULT);
        eepromWriteParameter();
    }
    setToneDetectorControlDefaults();

#ifdef DEBUG
    Serial.print(" min=");
    Serial.print(ToneDetectorControl.Frequency10HzMin);
    Serial.print("0Hz max=");
    Serial.print(ToneDetectorControl.Frequency10HzMax);
    Serial.print("0Hz duration=");
    Serial.print(PfeiffschalterControl.PeriodValidNeededMillis);
    Serial.println("ms");
#endif
    echoRangeIndex();

    //initPinChangeInterrupt
#if (__AVR_ATmega328P__) || defined (__AVR_ATmega328__)
    // Setup ADC, setMUX + Reference
    ADMUX = (ADMUX & ~0xCF) | (ADC_REFERENCE << 6) | ADC_CHANNEL;

    PCICR = 1 << PCIE2; //PCINT2 enable
    PCMSK2 = digitalPinToBitMask(BUTTON_PIN); // =0x20 - Pin 5 enable
#endif
#if defined(__AVR_ATtiny85__)
    // Setup ADC
    ADMUX = 0x01;// Reference = VCC, mux channel = Ain1 (PB2)

    GIMSK |= 1 << PCIE;//PCINT enable
    PCMSK = digitalPinToBitMask(BUTTON_PIN);
#endif
}

/************************************************************************
 * main loop
 ************************************************************************/
// noreturn saves program space!
void __attribute__((noreturn)) loop(void) {
    static uint32_t sMillisAtLastLEDChange;
    static uint32_t sMillisAtLastRelayChange;
    static uint32_t sMillisAtLastButtonRelease; // for reset timeout
    static bool sEnableRelayChange;
    static bool sMatchTooLong;

    for (;;) {
        uint32_t tMillis = millis();
        uint32_t tMillisSinceLastChange = tMillis - sMillisAtLastLEDChange;
#ifdef TRACE
        Serial.print(" State=");
        Serial.print(PfeiffschalterControl.MainState);
        Serial.print(" Count=");
        Serial.print(PfeiffschalterControl.LedBlinkCount);
        Serial.print(" Button=");
        Serial.print(ButtonControl.MillisOfButtonPress);
        Serial.print(" Last=");
        Serial.print(ButtonControl.MillisAtLastInterrupt);
        Serial.print(" Low=");
        Serial.println(ButtonControl.LowDetected);
#endif
        /*
         * Blink LED
         */
        if (PfeiffschalterControl.LedBlinkCount != 0) {
            if (tMillisSinceLastChange > PfeiffschalterControl.LedBlinkMillis) {
                toggleOutput(LED_FEEDBACK);
                sMillisAtLastLEDChange = tMillis;
                if (digitalReadFast(LED_FEEDBACK) == LOW) {
                    PfeiffschalterControl.LedBlinkCount--;
                    if (PfeiffschalterControl.LedBlinkCount <= -1) {
                        PfeiffschalterControl.LedBlinkCount = -1;
                    }
                }
            }
        }

        if (PfeiffschalterControl.MainState == TONE_DETECT) {
            /*
             * Analyze and match main loop
             * 25 ms for one loop at attiny85 1MHz
             */
            analyzeSignal(PRESCALE_VALUE); // 27.6 millis
            /*
             * plausibility check
             */
            doPlausi();

            digitalWriteFast(LED_DEBUG, LOW);
            if (ToneDetectorControl.Frequency10HzActual > SIGNAL_STRENGTH_LOW
                    && ToneDetectorControl.Frequency10HzActual <= SIGNAL_MAX_ERROR_CODE) {
                digitalWriteFast(LED_DEBUG, HIGH);
            }

#if (__AVR_ATmega328P__) || defined (__AVR_ATmega328__)
            digitalWriteFast(LED_PLAUSI_FIRST, LOW);
            digitalWriteFast(LED_PLAUSI_DISTRIBUTION, LOW);

            if (ToneDetectorControl.Frequency10HzActual == SIGNAL_FIRST_PLAUSI_FAILED) {
                digitalWriteFast(LED_PLAUSI_FIRST, HIGH);
            }
            if (ToneDetectorControl.Frequency10HzActual == SIGNAL_DISTRIBUTION_PLAUSI_FAILED) {
                digitalWriteFast(LED_PLAUSI_DISTRIBUTION, HIGH);
            }
#endif

#ifdef DEBUG
            Serial.print("F=");
            Serial.print(ToneDetectorControl.Frequency10HzActual);
            Serial.print("0Hz Filtered=");
            Serial.print(ToneDetectorControl.Frequency10HzFiltered);
            Serial.println("0Hz");
#endif

            /*
             * compute match
             */
            computeMatch(); // appr. 2 micros

#if (__AVR_ATmega328P__) || defined (__AVR_ATmega328__)
            digitalWriteFast(LED_LOWER, LOW);
            digitalWriteFast(LED_HIGHER, LOW);
#endif
            if (ToneDetectorControl.ToneMatch == TONE_MATCH_INVALID) {
                PfeiffschalterControl.LedBlinkCount = 0;
                digitalWriteFast(LED_FEEDBACK, LOW);
            }
#if (__AVR_ATmega328P__) || defined (__AVR_ATmega328__)
            if (ToneDetectorControl.ToneMatch == TONE_MATCH_LOWER) {
                digitalWriteFast(LED_LOWER, HIGH);
            } else if (ToneDetectorControl.ToneMatch == TONE_MATCH_HIGHER) {
                digitalWriteFast(LED_HIGHER, HIGH);
            }
#endif

            /*
             * process match state
             */
            if (ToneDetectorControl.ToneMatchFiltered == TONE_MATCH_INVALID) {
                PfeiffschalterControl.MatchValidCount = 0;
            } else if (ToneDetectorControl.ToneMatchFiltered == TONE_MATCH_HIGHER) {
                // set blink frequency according to gap between real and maximum-match pitch
                int16_t tLedBlinkMillis = TIMING_HIGHER_MILLIS
                        - ((ToneDetectorControl.Frequency10HzFiltered - ToneDetectorControl.Frequency10HzMax) / 2);
                if (tLedBlinkMillis < TIMING_HIGHER_MILLIS_MINIMUM) {
                    tLedBlinkMillis = TIMING_HIGHER_MILLIS_MINIMUM;
                }
#ifdef DEBUG
                Serial.print("tLedBlinkMillis=");
                Serial.println(tLedBlinkMillis);
#endif
                setFeedbackLedBlinkState(tLedBlinkMillis, -1);
                PfeiffschalterControl.MatchValidCount--;
            } else if (ToneDetectorControl.ToneMatchFiltered == TONE_MATCH_LOWER) {
                // set blink frequency according to gap between real and minimal-match pitch
                uint16_t tLedBlinkMillis = TIMING_LOWER_MILLIS
                        + ((ToneDetectorControl.Frequency10HzMin - ToneDetectorControl.Frequency10HzFiltered) * 4);
#ifdef DEBUG
                Serial.print("tLedBlinkMillis=");
                Serial.println(tLedBlinkMillis);
#endif
                setFeedbackLedBlinkState(tLedBlinkMillis, -1);
                PfeiffschalterControl.MatchValidCount--;
            } else {
                /*
                 * successful match here
                 */
                PfeiffschalterControl.LedBlinkCount = 0;
                PfeiffschalterControl.MatchValidCount++;
                digitalWriteFast(LED_FEEDBACK, HIGH);
            }

            /*
             * housekeeping
             */
            if (PfeiffschalterControl.MatchValidCount < 0) {
                PfeiffschalterControl.MatchValidCount = 0;
            }
            if (PfeiffschalterControl.MatchValidCount < PfeiffschalterControl.MatchValidNeeded) {
                sEnableRelayChange = true;
                sMatchTooLong = false;
            } else {
                /*
                 * set output relay
                 */
                tMillisSinceLastChange = tMillis - sMillisAtLastRelayChange;
                if (sEnableRelayChange) {
                    sEnableRelayChange = false;
                    if (tMillisSinceLastChange > TIMING_RELAY_DEAD_MILLIS) {
                        toggleOutput(RELAY_OUT);
                        sMillisAtLastRelayChange = tMillis;
                    }
                } else {
                    if (!sMatchTooLong && tMillisSinceLastChange > MATCH_TO_LONG_MILLIS) {
                        /*
                         * match lasted too long, reset relay to previous state
                         */
                        toggleOutput(RELAY_OUT);
                        sMatchTooLong = true;
                    }
                }
            }

            /*
             * process button in analyze loop
             */
            if (digitalReadFast(BUTTON_PIN) != LOW) {
                // if the ISR miss the last low to high transition because of heavy bouncing
                ButtonControl.LowDetected = false;
            }

            if (ButtonControl.ButtonReleaseJustDetected) {
                ButtonControl.ButtonReleaseJustDetected = false;
                /*
                 * decide next state / toggle relay
                 */
                if (ButtonControl.MillisOfButtonPress > BUTTON_PUSH_ENTER_PROGRAM_ADVANCED_MILLIS) {
                    PfeiffschalterControl.MainState = PROGRAM_ADVANCED_TONE_RANGE;
                    ToneDetectorControl.Frequency10HzMin = 1000;
                    ToneDetectorControl.Frequency10HzMax = 0;
                    PfeiffschalterControl.ButtonPressCounter = 0;
                    setFeedbackLedBlinkState(TIMING_LOWER_MILLIS * 3, 2);
                } else if (ButtonControl.MillisOfButtonPress > BUTTON_PUSH_ENTER_PROGRAM_SIMPLE_MILLIS) {
                    PfeiffschalterControl.MainState = PROGRAM_SIMPLE;
                    PfeiffschalterControl.ButtonPressCounter = 0;
                    setFeedbackLedBlinkState(TIMING_LOWER_MILLIS * 3, 1);
                } else if (ButtonControl.MillisOfButtonPress < RESET_ENTER_BUTTON_PUSH_MILLIS) {
                    PfeiffschalterControl.MainState = WAIT_FOR_SECOND_PUSH_FOR_RESET;
                    sMillisAtLastButtonRelease = millis();
                } else {
                    toggleOutput(RELAY_OUT);
                }
#ifdef DEBUG
                Serial.print(" Button=");
                Serial.print(ButtonControl.MillisOfButtonPress);
                Serial.print(" State=");
                Serial.println(PfeiffschalterControl.MainState);
#endif
            }

            /**************************************
             * other states (reset / programming)
             **************************************/

        } else if (PfeiffschalterControl.MainState == WAIT_FOR_SECOND_PUSH_FOR_RESET) {
            // IF second push happens before timeout then perform reset, otherwise just switch relay
            tMillis = millis();
            if (tMillis - sMillisAtLastButtonRelease > RESET_WAIT_TIMEOUT_MILLIS) {
                // Reset state and toggle relay
                PfeiffschalterControl.MainState = TONE_DETECT;
                toggleOutput(RELAY_OUT);
#ifdef DEBUG
                Serial.println(" timeout for reset");
#endif
            } else if (ButtonControl.ButtonReleaseJustDetected) {
#if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega328__)
                PfeiffschalterControl.MainState = TONE_DETECT;
                // simulate reset
                digitalWriteFast(RELAY_OUT, LOW);
                // second push happened before timeout -> echo range index
                echoRangeIndex();
#else
                // second push happened before timeout -> perform reset (this does not work with arduino bootloader)
                wdt_enable(WDTO_500MS);
                while (1) {
                };
#endif
            }
        } else if (PfeiffschalterControl.MainState == PROGRAM_WAIT_FOR_FEEDBACK_END) {
            /*
             * Wait for feedback to end
             */
            if (PfeiffschalterControl.LedBlinkCount <= 0) {
                backToStateDetect();
            }
        } else {
            /*
             * TIMEOUT handling for program states
             */
            // get new values, since the ISR can have changed it
            uint32_t tLastIntMillis = ButtonControl.MillisAtLastInterrupt;
            tMillis = millis();
            if (tMillis - tLastIntMillis > PROGRAM_MODE_TIMEOUT_MILLIS) {
                ButtonControl.MillisAtLastInterrupt = tMillis; // avoid endless timeouts
                setFeedbackLedBlinkState(TIMING_LOWER_MILLIS, 1);
                PfeiffschalterControl.MainState = PROGRAM_WAIT_FOR_FEEDBACK_END;
#ifdef DEBUG
                Serial.println("timeout");
#endif
            } else if (PfeiffschalterControl.MainState == PROGRAM_SIMPLE) {
                /*
                 * PROGRAM_SIMPLE
                 */
                if (ButtonControl.ButtonReleaseJustDetected) {
                    // increment range count
                    ButtonControl.ButtonReleaseJustDetected = false;
                    PfeiffschalterControl.ButtonPressCounter++;
                    if (PfeiffschalterControl.ButtonPressCounter > sizeof(predefinedRangesStart)) {
                        PfeiffschalterControl.ButtonPressCounter = sizeof(predefinedRangesStart);
                    }
#ifdef DEBUG
                    Serial.print(" Count=");
                    Serial.println(PfeiffschalterControl.ButtonPressCounter);
#endif
                    // echo button
                    setFeedbackLedBlinkState(TIMING_LOWER_MILLIS * 3, 1);
                } else if (PfeiffschalterControl.ButtonPressCounter > 0) {
                    // end detection (timeout handling) if a range has been chosen
                    if (tMillis - tLastIntMillis > PROGRAM_MODE_SIMPLE_END_DETECT_MILLIS) {
                        // set new range
                        ToneDetectorControl.Frequency10HzMin =
                                predefinedRangesStart[PfeiffschalterControl.ButtonPressCounter - 1];
                        ToneDetectorControl.Frequency10HzMax =
                                predefinedRangesEnd[PfeiffschalterControl.ButtonPressCounter - 1];
                        setPeriodValidNeededMillis(MATCH_MILLIS_NEEDED_DEFAULT);
                        eepromWriteParameter();
                        // echo recognized parameter
                        setFeedbackLedBlinkState(TIMING_LOWER_MILLIS * 3, PfeiffschalterControl.ButtonPressCounter);
                        PfeiffschalterControl.MainState = PROGRAM_WAIT_FOR_FEEDBACK_END;
                    }
                }

            } else if (PfeiffschalterControl.MainState == PROGRAM_ADVANCED_TONE_RANGE) {
                /*
                 * PROGRAM_ADVANCED_TONE_RANGE
                 */
                if (digitalReadFast(BUTTON_PIN) == LOW) {
                    //get range values
                    digitalWriteFast(LED_FEEDBACK, HIGH);
                    PfeiffschalterControl.ButtonPressCounter = 1;
                    analyzeSignal(PRESCALE_VALUE); // 27.6 millis
                    if (ToneDetectorControl.Frequency10HzActual > SIGNAL_MAX_ERROR_CODE) {
                        // if frequency changes to fast it is assumed a dropout
                        if (((PfeiffschalterControl.Frequency10HzLast + MAX_ACCEPTABLE_DELTA_FREQ)
                                - ToneDetectorControl.Frequency10HzActual) < (2 * MAX_ACCEPTABLE_DELTA_FREQ)
                                || ToneDetectorControl.Frequency10HzMax == 0) {
                            PfeiffschalterControl.Frequency10HzLast = ToneDetectorControl.Frequency10HzActual;
                            if (ToneDetectorControl.Frequency10HzActual > ToneDetectorControl.Frequency10HzMax) {
                                ToneDetectorControl.Frequency10HzMax = ToneDetectorControl.Frequency10HzActual;
                            }
                            if (ToneDetectorControl.Frequency10HzActual < ToneDetectorControl.Frequency10HzMin) {
                                ToneDetectorControl.Frequency10HzMin = ToneDetectorControl.Frequency10HzActual;
                            }
                        }
                    }
#ifdef DEBUG
                    Serial.print(" last=");
                    Serial.print(PfeiffschalterControl.Frequency10HzLast);
                    Serial.print(" act=");
                    Serial.print(ToneDetectorControl.Frequency10HzActual);
                    Serial.print(" min=");
                    Serial.print(ToneDetectorControl.Frequency10HzMin);
                    Serial.print("0Hz max=");
                    Serial.print(ToneDetectorControl.Frequency10HzMax);
                    Serial.println("0Hz");

#endif
                } else if (PfeiffschalterControl.ButtonPressCounter > 0) {
                    digitalWriteFast(LED_FEEDBACK, LOW);
                    PfeiffschalterControl.MainState = PROGRAM_ADVANCED_TONE_DURATION;
                    ButtonControl.ButtonReleaseJustDetected = false; // reset flag
                }
            } else if (PfeiffschalterControl.MainState == PROGRAM_ADVANCED_TONE_DURATION) {
                // end detection (timeout handling) if a range has been chosen
                if (tMillis - tLastIntMillis > PROGRAM_MODE_ADVANCED_END_DETECT_MILLIS) {
                    // set duration to default because it is not specified by additional button press
                    setPeriodValidNeededMillis(MATCH_MILLIS_NEEDED_DEFAULT);
                    // write parameter for range to eeprom here since values are valid and programming ends here
                    eepromWriteParameter();
                    // echo duration
                    setFeedbackLedBlinkState(PfeiffschalterControl.PeriodValidNeededMillis, 1);
                    PfeiffschalterControl.MainState = PROGRAM_WAIT_FOR_FEEDBACK_END;
                }
                if (ButtonControl.ButtonReleaseJustDetected) {
                    /*
                     * PROGRAM_ADVANCED_TONE_DURATION
                     */
                    ButtonControl.ButtonReleaseJustDetected = false;
                    setPeriodValidNeededMillis(ButtonControl.MillisOfButtonPress);
                    // write parameter with duration to eeprom
                    eepromWriteParameter();
#ifdef DEBUG
                    Serial.print(" MillisOfButtonPress=");
                    Serial.print(ButtonControl.MillisOfButtonPress);
                    Serial.print(" duration=");
                    Serial.println(PfeiffschalterControl.PeriodValidNeededMillis);
#endif
                    // echo duration
                    setFeedbackLedBlinkState(PfeiffschalterControl.PeriodValidNeededMillis, 1);
                    PfeiffschalterControl.MainState = PROGRAM_WAIT_FOR_FEEDBACK_END;
                }
            }
        }
    } // for(;;)
}

/*
 * button change handler
 * do debouncing
 * detect low period duration
 * Big Problem: you are not sure that the level you read, is the level which triggers the interrupt
 */
//
#if defined(__AVR_ATtiny85__)
ISR(PCINT0_vect) {
#else
ISR(PCINT2_vect) {
#endif
    uint32_t tMillis = millis();
    uint32_t tMillisSinceLastInterrupt = tMillis - ButtonControl.MillisAtLastInterrupt;
    ButtonControl.MillisAtLastInterrupt = tMillis;
    // use delayMicroseconds() since we are in an interrupt service routine and delay() using timer is not working
    delayMicroseconds(BUTTON_PUSH_DEBOUNCE_MILLIS * 1000);
    if (digitalReadFast(BUTTON_PIN) == LOW) {
        ButtonControl.LowDetected = true;
    } else if (ButtonControl.LowDetected) {
// action here
        ButtonControl.LowDetected = false;
        ButtonControl.MillisOfButtonPress = tMillisSinceLastInterrupt;
        ButtonControl.ButtonReleaseJustDetected = true;
    }
}

